package com.zzr.demo.sso.server01.web;

import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author: xiaomi
 * @date: 2021/8/22
 * @description: SSO 登录页
 */
@Controller
@Slf4j
public class LoginController {

    @Autowired
    OkHttpClient okHttpClient;

    public static final String TOKEN_KEY = "sso_token";
    private static final String CACHE_PREFIX = "sso:toen:";
    public static final String CLIENT_TOKEN_KEY = "client_token";
    public static List<Cookie> clientCookies = new ArrayList<>();

    static {
        //client1.com
        Cookie cookie1 = new Cookie(CLIENT_TOKEN_KEY, "");
        cookie1.setMaxAge(0);
        cookie1.setDomain("client1.com");
        clientCookies.add(cookie1);

        Cookie cookie2 = new Cookie(CLIENT_TOKEN_KEY, "");
        cookie2.setMaxAge(0);
        cookie2.setDomain("client2.com");
        clientCookies.add(cookie2);
    }

    /**
     * 由于 Refer 不可行
     */
    @Deprecated
    public static final String SERVER_LOGIN_PAGE = "http://localhost:8080/login.html";

    @Autowired
    StringRedisTemplate stringRedisTemplate;


    /**
     * 登录页路由
     * NOTEME 这里拿到 cookie 的方式：false-sion.getAttribute(TOKEN_KEY);true:@CookieValue(value = "sso_token", required = false) String sso_token
     *
     * @return
     */
    //@GetMapping({"/", "/login.html"})
    @GetMapping("/login.html")
    public String loginPage(HttpServletRequest request, HttpSession session, @CookieValue(value = "sso_token", required = false) String sso_token,
                            @RequestParam("callback") String callback) throws MalformedURLException {
        log.info("来到登录页面...");
        //1.判断是否存在 session 了
        //TODO: 这里如何拿到未登录服务的session 信息？？？？
        //Object tokenAttribute = session.getAttribute(TOKEN_KEY);//这种方式是拿不到的！
        if (StringUtils.isEmpty(sso_token)) {
            //说明没有登录过
            return "login";
        }
        //存在 session
        //2.拿到 token 去验证用户信息
        String token = sso_token;
        String userName = getUserName(token);
        if (StringUtils.isEmpty(userName)) {
            //2.1 如果用户不存在
            return "login";
        }

        //当访问登录页时，如果发现已经存在 sso_token 的 cookie ，就进行验证
        //Location - 跳转后的地址:http://localhost:8080/login.html?callback=http://localhost:8082/
//        String location = request.getHeader("Location");
//        URL url = new URL(location);
//        String callback = getCallback(url.getQuery());

        return "redirect:" + callback + "?token=" + token;
    }


    /**
     * 1.废弃 callback :, @RequestParam(value = "callback", required = false) String callback
     *
     * @param username
     * @param password
     * @param request
     * @param response
     * @param attributes
     * @return
     */
    @PostMapping(value = "/doLogin")
    public String doLogin(@RequestParam("username") String username, @RequestParam("password") String password, HttpServletRequest request, HttpServletResponse response,
                          RedirectAttributes attributes) throws URISyntaxException, MalformedURLException {
        String callback = "";

        String referer = request.getHeader("Referer");
        if (StringUtils.isEmpty(referer)) {
            //如果来源为空，说明不合法！
            attributes.addFlashAttribute("error", "来源不合法!");
            return "redirect:/login.html";
        }
        URL url = new URL(referer);
        String query = url.getQuery();
        //query = callback=http://localhost:8081/
        callback = getCallback(query);

        //1.获取 refer 中的跳转地址
        //暂且让任一来源合法
        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            //验证不通过
            attributes.addFlashAttribute("error", "用户信息验证不通过!");
            return "redirect:/login.html";
        }
        //完成登录
        //1.生成 token
        //2.记录到 redis
        String uuid = UUID.randomUUID().toString().replace("_", "");
        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
        opsForValue.set(CACHE_PREFIX + uuid, username, 60, TimeUnit.MINUTES);

        //3.设置 cookie
        //NOTEME 这一步很重要！只要有人登录成功了，在浏览器跳转的时候，浏览器就会自动将跳转的地址的域的 地址 cookie 带上！
        //NOTEME 这就是所有系统都能保持登录的秘密！
        Cookie sso_token = new Cookie(TOKEN_KEY, uuid);
        response.addCookie(sso_token);

        //4.返回来源页面，并返回token
        return "redirect:" + callback + "?token=" + uuid;
    }

    /**
     * 自动接收跳转的 cookie
     *
     * @param sso_token
     * @return
     */
    @GetMapping("/logout")
    public String logout(@CookieValue(value = "sso_token", required = false) String sso_token, HttpServletResponse response) throws IOException {
        String key = CACHE_PREFIX + sso_token;
        stringRedisTemplate.delete(key);

        //delete cookie
        //session.removeAttribute(TOKEN_KEY);
        //delete sso.server
        Cookie cookie = new Cookie(TOKEN_KEY, sso_token);
        cookie.setMaxAge(0);
        response.addCookie(cookie);

        //delete client
        //NOTEME:设置失败，无法跨域设置 coolie ！看来只能使用接口了!
        for (Cookie clientCookie : clientCookies) {
            response.addCookie(clientCookie);
        }
        //调用客户端的登出接口完成
        Request request1 = new Request.Builder()
                .url("http://client1.com:8081/removeClientToken?token=" + sso_token)
                .method("GET", null)
                .build();
        Response response1 = okHttpClient.newCall(request1).execute();

        Request request2 = new Request.Builder()
                .url("http://client2.com:8082/removeClientToken?token=" + sso_token)
                .method("GET", null)
                .build();
        Response response2 = okHttpClient.newCall(request2).execute();

        return "login";
    }


    @ResponseBody
    @GetMapping("/getUserName")
    public String getUserName(@RequestParam("token") String token) {
        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
        String key = CACHE_PREFIX + token;
        String name = opsForValue.get(key);
        return name;
    }

    private String getCallback(String query) {
        String callback = "";
        String[] strings = query.split("&");
        for (String param : strings) {
            if (param.contains("callback=")) {
                callback = param.replace("callback=", "");
                break;
            }
        }
        return callback;
    }
}
